package com.piesat.quake.business.device.mqtt.pipeline.datahandler;

import com.piesat.quake.business.device.mqtt.DataType;
import com.piesat.quake.business.device.mqtt.LengthData;
import com.piesat.quake.business.device.mqtt.NumberType;
import com.piesat.quake.business.device.mqtt.anno.CharField;
import com.piesat.quake.business.device.mqtt.anno.ConvertField;
import com.piesat.quake.business.device.mqtt.anno.IgnoreField;
import com.piesat.quake.business.device.mqtt.anno.LengthField;
import com.piesat.quake.business.device.mqtt.anno.NodeField;
import com.piesat.quake.business.device.mqtt.anno.NumberField;
import com.piesat.quake.business.device.mqtt.convert.Convert;
import com.piesat.quake.business.device.mqtt.param.CharParseParam;
import com.piesat.quake.business.device.mqtt.param.ConvertParseParam;
import com.piesat.quake.business.device.mqtt.param.LengthParseParam;
import com.piesat.quake.business.device.mqtt.param.NumberParseParam;
import com.piesat.quake.business.device.mqtt.param.ParseParam;
import com.piesat.quake.business.device.mqtt.reader.Reader;
import com.piesat.quake.business.device.mqtt.writer.Writer;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.LinkedList;
import java.util.List;

import io.netty.buffer.ByteBuf;

/**
 * 基础数据处理
 *
 * @author Gersy
 * @date 2023/3/6
 * @since 1.0
 */
public class DefaultDataHandler implements DataHandler {
    protected final Class<?> clazz;
    protected Field[] fields;
    protected List<ParseParam> parseParams;
    private String nodeFieldName;
    private List<DataHandler> children;
    private final String[] excludeFields;

    public DefaultDataHandler(Class<?> clazz) {
        this(clazz, null);
    }

    public DefaultDataHandler(Class<?> clazz, String[] excludeFields) {
        this.clazz = clazz;
        this.excludeFields = excludeFields;
        handleFields(clazz);
    }

    private void handleFields(Class<?> clazz) {
        Field[] declaredFields = clazz.getDeclaredFields();
        List<Field> fieldList = new LinkedList<>();
        parseParams = new LinkedList<>();
        for (Field field : declaredFields) {
            if (!Modifier.isStatic(field.getModifiers())) {
                if (checkIfExclude(field.getName())) continue;
                field.setAccessible(true);
                Class<?> type = field.getType();
                //IgnoreField 注解的跳过
                if (field.getAnnotation(IgnoreField.class) != null || field.getAnnotation(NodeField.class) != null) {
                    continue;
                }
                ConvertField convertField = field.getAnnotation(ConvertField.class);
                if (convertField != null) {
                    parseParams.add(new ConvertParseParam(field, convertField.convert(), convertField.length()));
                } else {
                    if (type == String.class) {
                        CharField annotation = field.getAnnotation(CharField.class);
                        if (annotation == null) {
                            throw new RuntimeException("字符串必须添加@CharField或@ConvertField注解:" + clazz + ":" + field.getName());
                        }
                        parseParams.add(CharParseParam.create(field, annotation.value()));
                    } else {
                        LengthField fieldAnnotation = field.getAnnotation(LengthField.class);
                        if (fieldAnnotation != null) {
                            parseParams.add(LengthParseParam.create(field));
                        } else {
                            NumberField annotation = field.getAnnotation(NumberField.class);
                            parseParams.add(NumberParseParam.create(field, annotation != null ? annotation.value() : getNumberType(field.getType())));
                        }
                    }
                }
                fieldList.add(field);
            }
        }
        fields = fieldList.toArray(new Field[0]);
    }

    private boolean checkIfExclude(String name) {
        if (excludeFields != null) {
            boolean isExclude = false;
            for (String excludeField : excludeFields) {
                if (excludeField.equals(name)) {
                    isExclude = true;
                    break;
                }
            }
            return isExclude;
        }
        return false;
    }

    private int getNumberType(Class<?> clazz) {
        if (clazz == Byte.class || clazz == byte.class) {
            return NumberType.BYTE;
        } else if (clazz == Short.class || clazz == short.class) {
            return NumberType.SHORT;
        } else if (clazz == Integer.class || clazz == int.class) {
            return NumberType.INT;
        } else if (clazz == Long.class || clazz == long.class) {
            return NumberType.LONG;
        } else if (clazz == Float.class || clazz == float.class) {
            return NumberType.FLOAT;
        } else if (clazz == Double.class || clazz == double.class) {
            return NumberType.DOUBLE;
        }
        throw new RuntimeException("获取数字类型出错:" + clazz.getName());
    }

    @Override
    public String getNodeFieldName() {
        return nodeFieldName;
    }

    @Override
    public void setNodeFieldName(String fieldName) {
        nodeFieldName = fieldName;
    }

    @Override
    public Object decode(ByteBuf byteBuf, Object data) {
        try {
            if (data == null) {
                Constructor<?> constructor = clazz.getConstructor();
                data = constructor.newInstance();
            }
            for (int i = 0; i < parseParams.size(); i++) {
                ParseParam parseParam = parseParams.get(i);
                Field field = fields[i];
                Object value = null;
                if (parseParam instanceof Convert) {
                    value = ((Convert) parseParam).read(byteBuf, parseParam);
                } else {
                    Reader reader = DataType.getReader(parseParam.type);
                    value = reader.read(byteBuf, parseParam);
                }
                field.set(data, value);
            }
            return data;
        } catch (Exception e) {
            throw new RuntimeException("解析失败：" + e.getMessage());
        }
    }

    @Override
    public void encode(ByteBuf byteBuf, Object data) {
        try {
            for (int i = 0; i < parseParams.size(); i++) {
                ParseParam parseParam = parseParams.get(i);
                Field field = fields[i];
                if (parseParam instanceof LengthParseParam) {
//                    int length = 0;
//                    if (parseParam.getLength() != -1) {
//                        length = parseParam.getLength();
//                    } else {
//                        length = getLength();
//                        ((LengthParseParam) parseParam).setLength(length);
//                    }
                    if (data instanceof LengthData) {
                        DataType.NUMBER.getWriter().write(byteBuf, parseParam, ((LengthData) data).getLength());
                    } else {
                        DataType.NUMBER.getWriter().write(byteBuf, parseParam, getLength());
                    }
                } else {
                    if (parseParam instanceof Convert) {
                        ((Convert) parseParam).write(byteBuf, field.get(data), parseParam);
                    } else {
                        Writer writer = DataType.getWriter(parseParam.type);
                        writer.write(byteBuf, parseParam, field.get(data));
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("解析失败：" + e.getMessage());
        }
    }

    @Override
    public void addChild(DataHandler handler) {
        if (children == null) {
            children = new LinkedList<>();
        }
        children.add(handler);
    }

    @Override
    public List<DataHandler> getChildren() {
        return children;
    }

    @Override
    public int getLength() {
        int length = 0;
        for (ParseParam parseParam : parseParams) {
            length += parseParam.getLength();
        }
        if (children != null) {
            for (DataHandler child : children) {
                length += child.getLength();
            }
        }
        return length;
    }

}
